This class is meant as an emulation of Pen. last mod: 16-jan-08 sciss
| no-op / not working | |
| String -> *bounds | calls a cocoa primitive. there is no replacement in SwingOSC due to the fact that this would need to be implemented asynchronously |
| different behaviour | |
| stroke | swing's stroke is a tiny bit thicker than cocoa's ; with antialiasing off the stroke is about 1px thicker |
| color | Color.set does NOT work (it calls a cocoa primitive); instead we use JPen.color_ to set the color (similiarily JPen.strokeColor_ and JPen.fillColor_). |
| strings |
|
| extended functionality | |
| curveTo, quadCurveTo | have been implemented |
| known issues / todo | |
| speed | especially for large bodies of drawing functions this can be significantly slower than the cocoa class |
| string location | the calculation in stringAtPoint is a bit unclear; adding ascent plus descent results in y-position similiar to cocoa, albeit a bit illogic |
| font size | font sizes and line heights (when using stringInRect) are a little bit smaller than in cocoa |
Note: please use the abstraction layer GUI.pen if possible! (see GUI)
A helper class to draw on a JSCWindow, a JSCUserView, or a JSCTabletView. It cannot be instantiated but only provides static methods.
The following methods must be called within an JSCWindow-drawHook or a JSCUserView-drawFunc function, and will only be visible once the window or the view is refreshed. Each call to JSCWindow-refresh or JSCUserView-refresh will 'overwrite' all previous drawing by executing the currently defined function.
See also: JSCUserView, JSCWindow, JSCTabletView, Color, JFont, Point, Rect
The following methods define paths. You will need to call *stroke or *fill to actually draw them:
JPen.moveTo( <(Point) point> )
Moves the pen to point. Use this method to start a new (unconnected) polyline. See *stroke for an example.
JPen.lineTo( <(Point) point> )
Extends the shape by drawing a line from the current position to a given point. See *stroke for an example.
JPen.line( <(Point) p1>, <(Point) p2> )
Adds a line between the points p1 and p2. The new current position is set to p2. Example:
(
w = JSCWindow.new.front;
w.view.background_( Color.white );
w.drawHook = {
var x1, y1, x2, y2, x1a, y1a, x2a, y2a, txr, tyr, rr;
JPen.translate( 200, 200 );
JPen.scale( 0.5, 0.5 );
x1 = 175.0.bilinrand;
x2 = 175.0.bilinrand;
y1 = 175.0.bilinrand;
y2 = 175.0.bilinrand;
x1a = 15.0.bilinrand;
x2a = 15.0.bilinrand;
y1a = 15.0.bilinrand;
y2a = 15.0.bilinrand;
txr = 2.0.bilinrand;
tyr = 2.0.bilinrand;
rr = 0.05pi.bilinrand;
JPen.moveTo( 175 @ 0 );
200.do { arg i;
JPen.translate( txr, tyr );
JPen.rotate( rr );
JPen.line( x1 @ y1, x2 @ y2 );
x1 = x1 + x1a;
x2 = x2 + x2a;
y1 = y1 + y1a;
y2 = y2 + y2a;
};
JPen.stroke;
};
)
(
var run = true;
w.onClose = { run = false }; // closing window stops animation
{ while { run } { w.refresh; 0.75.wait }}.fork( AppClock );
)
JPen.curveTo( <(Point) point>, <(Point) cpoint1>, <(Point) cpoint2> )
Adds a cubic-spline curve from the current position to point. cpoint1 and cpoint2 are help-points determining the curve's curvature. Example:
(
w = JSCWindow.new.front;
w.view.background_( Color.white );
w.drawHook = {
var x1, y1, x2, y2, x1a, y1a, x2a, y2a, txr, tyr, rr;
JPen.translate( 200, 200 );
JPen.scale( 0.5, 0.5 );
x1 = 175.0.bilinrand;
x2 = 175.0.bilinrand;
y1 = 175.0.bilinrand;
y2 = 175.0.bilinrand;
x1a = 25.0.bilinrand;
x2a = 25.0.bilinrand;
y1a = 25.0.bilinrand;
y2a = 25.0.bilinrand;
txr = 4.0.bilinrand;
tyr = 4.0.bilinrand;
rr = 0.05pi.bilinrand;
JPen.moveTo( 175 @ 0 );
100.do {
JPen.translate( txr, tyr );
JPen.rotate( rr );
JPen.curveTo( 175 @ 0, x1 @ y1, x2 @ y2 );
x1 = x1 + x1a;
x2 = x2 + x2a;
y1 = y1 + y1a;
y2 = y2 + y2a;
};
JPen.stroke;
};
)
(
var run = true;
w.onClose = { run = false }; // closing window stops animation
{ while { run } { w.refresh; 0.75.wait }}.fork( AppClock );
)
JPen.quadCurveTo( <(Point) point>, <(Point) cpoint1> )
Adds a quadratic-spline curve from the current position to point. cpoint1 is a help-point determining the curve's curvature. Example:
(
var ang1a, ang2a, ang1s, ang2s, rad1a, rad2a, trans;
ang1a = Array.fill( 4, 0.0 );
ang2a = Array.fill( 4, 0.0 );
ang1s = Array.fill( 4, 0.0 );
ang2s = Array.fill( 4, 0.0 );
rad1a = Array.fill( 4, 0.0 );
rad2a = Array.fill( 4, 0.0 );
trans = [ 225 @ 225, 350 @ 0, 0 @ 350, -350 @ 0 ];
w = JSCWindow.new.front;
w.view.background_( Color.white );
d = 0.0;
w.drawHook = {
var ang1, ang2, rad1, rad2;
var di = 1.0 - d;
JPen.scale( 0.5, 0.5 );
4.do({ arg i;
JPen.translate( trans[ i ].x, trans[ i ].y );
ang1s[ i ] = ang1s[ i ] * d + (pi.bilinrand * di);
ang2s[ i ] = ang2s[ i ] * d + (pi.bilinrand * di);
ang1 = ang1s[ i ];
ang2 = ang2s[ i ];
rad1 = 150;
rad2 = 75;
ang1a[ i ] = ang1a[ i ] * d + ((pi/16).bilinrand * di);
ang2a[ i ] = ang2a[ i ] * d + ((pi/16).bilinrand * di);
rad1a[ i ] = (rad1a[ i ] * d + (20.0.bilinrand * di)).clip( -20.0, 20.0 );
rad2a[ i ] = (rad2a[ i ] * d + (10.0.bilinrand * di)).clip( -10.0, 10.0 );
100.do {
JPen.moveTo( 0 @ 0 );
JPen.quadCurveTo( (ang1.cos * rad1) @ (ang1.sin * rad1),
(ang2.cos * rad2) @ (ang2.sin * rad2) );
ang1 = ang1 + ang1a[ i ];
ang2 = ang2 + ang2a[ i ];
rad1 = (rad1 + rad1a[ i ]).clip( 0, 150 );
rad2 = (rad2 + rad2a[ i ]).clip( 0, 150 );
};
JPen.stroke;
});
};
)
(
var run = true;
w.onClose = { run = false }; // closing window stops animation
d = 0.97; // animation decay
{ while { run } { w.refresh; 0.1.wait }}.fork( AppClock );
)
d = 0.8; // faster
d = 0.99; // really slow
JPen.addArc( <(Point) center>, <(Number) radius>, <(Number) startAngle>, <(Number) arcAngle> )
Adds an arc around the center, at radius number of pixels. startAngle and arcAngle refer to the starting angle and the extent of the arc, and are in radians [0..2pi]. Example:
(
w = JSCWindow.new.front;
w.view.background_( Color.white );
w.drawHook = {
JPen.translate( 100, 100 );
10.do {
JPen.color = Color.red( rrand( 0.0, 1 ), rrand( 0.0, 0.5 ));
JPen.addArc( 100.rand @ 100.rand, rrand( 10, 100 ), 2pi.rand, pi );
JPen.perform([ \stroke, \fill ].choose );
};
};
)
(
var run = true;
w.onClose = { run = false }; // closing window stops animation
{ while { run } { w.refresh; 0.1.wait }}.fork( AppClock );
)
JPen.addWedge( <(Point) center>, <(Number) radius>, <(Number) startAngle>, <(Number) arcAngle> )
Adds a wedge around the center, at radius number of pixels. startAngle and arcAngle refer to the starting angle and the extent of the arc, and are in radians [0..2pi]. Example:
(
w = JSCWindow.new.front;
w.view.background_( Color.white );
w.drawHook = {
// set the Color
JPen.translate( 100, 100 );
10.do {
JPen.color = Color.blue( rrand( 0.0, 1 ), rrand( 0.0, 0.5 ));
JPen.addWedge( 100.rand @ 100.rand, rrand( 10, 100 ), 2pi.rand, 2pi.rand );
JPen.perform([ \stroke, \fill ].choose );
};
};
)
(
var run = true;
w.onClose = { run = false }; // closing window stops animation
{ while { run } { w.refresh; 0.1.wait }}.fork( AppClock );
)
JPen.addAnnularWedge( <(Point) center>, <(Number) innerRadius>, <(Number) outerRadius>, <(Number) startAngle>, <(Number) arcAngle> )
Adds an annular wedge around the center, from innerRadius to outerRadius in pixels. startAngle and arcAngle refer to the starting angle and the extent of the arc, and are in radians [0..2pi]. Example:
(
w = JSCWindow.new.front;
w.view.background_( Color.white );
w.drawHook = {
JPen.translate( 100, 100 );
500.do {
JPen.color = Color.green( rrand( 0.0, 1 ), rrand( 0.0, 0.5 ));
JPen.addAnnularWedge(
100.rand @ 100.rand,
rrand( 10, 50 ),
rrand( 51, 100 ),
2pi.rand,
2pi.rand
);
JPen.perform([ \stroke, \fill ].choose );
};
};
)
(
var run = true;
w.onClose = { run = false }; // closing window stops animation
{ while { run } { w.refresh; 1.0.wait }}.fork( AppClock );
)
JPen.addRect( <(Rect) rect> )
Adds a rectangle to the drawing. Example:
(
w = JSCWindow.new.front;
w.view.background_( Color.white );
w.drawHook = {
var x1, y1, x2, y2, x1a, y1a, x2a, y2a, txr, tyr, x1o, y1o, x2o, y2o, size;
JPen.translate( 200, 200 );
JPen.scale( 0.25, 0.25 );
JPen.width = 2.0;
x1 = 175.0.bilinrand;
x2 = 175.0.bilinrand;
y1 = 175.0.bilinrand;
y2 = 175.0.bilinrand;
x1a = 15.0.rand;
x2a = 15.0.rand;
y1a = 15.0.rand;
y2a = 15.0.rand;
txr = -15.0.rand;
tyr = -15.0.rand;
JPen.moveTo( 175 @ 0 );
200.do { arg i;
JPen.translate( txr, tyr );
x1o = x1;
y1o = y1;
x2o = x2;
y2o = y2;
x1 = x1 + x1a;
x2 = x2 + x2a;
y1 = y1 + y1a;
y2 = y2 + y2a;
size = max( (x2 - x1).abs, (y2 - y1).abs );
JPen.addRect( Rect( x1, y1, size, size ));
};
JPen.stroke;
};
)
(
var run = true;
w.onClose = { run = false }; // closing window stops animation
{ while { run } { w.refresh; 0.1.wait }}.fork( AppClock );
)
JPen.stroke
Outline the path previously defined with any of the above commands. The outline is stroked using the current pen-width (see *width) and current stroke-color. The stroke-color is set using JPen.strokeColor_ or JPen.color_ (the latter sets both stroke- and fill-color). Example:
(
w = JSCWindow.new.front;
w.view.background_( Color.white );
w.drawHook = {
// set the Color
JPen.color = Color.red;
JPen.moveTo( 200 @ 100 );
JPen.lineTo( 250 @ 200 );
JPen.lineTo( 300 @ 200 );
JPen.lineTo( 200 @ 250 );
JPen.lineTo( 100 @ 200 );
JPen.lineTo( 150 @ 200 );
JPen.lineTo( 200 @ 100 );
JPen.stroke;
};
)
JPen.fill
Fills the path previously defined with any of the above commands. The area is filled using the current fill-color. The fill-color is set using JPen.fillColor_ or JPen.color_ (the latter sets both stroke- and fill-color). Example:
(
w = JSCWindow.new.front;
w.view.background_( Color.white );
w.drawHook = {
// set the Color
JPen.color = Color.red;
JPen.moveTo( 200 @ 100 );
JPen.lineTo( 250 @ 200 );
JPen.lineTo( 300 @ 200 );
JPen.lineTo( 200 @ 250 );
JPen.lineTo( 100 @ 200 );
JPen.lineTo( 150 @ 200 );
JPen.lineTo( 200 @ 100 );
JPen.fill;
};
)
The following methods do not require separate stroke or fill calls. They directly paint a primitive geometric form.
JPen.strokeRect( <(Rect) rect> )
Strokes the outline of a rectangle. Example:
(
w = JSCWindow( "strokeRect", Rect( 128, 64, 360, 360 ));
w.drawHook = {
var r;
r = Rect( 100, 100, 160, 80 );
JPen.color = Color.black.alpha_( 0.8 );
JPen.strokeRect( r );
};
w.front;
)
Notice how the coordinates appear to lie between two pixels not in a pixel's center, so the stroke appears blurred. You can avoid this behaviour by shifting the coordinates by 0.5, 0.5:
(
w = JSCWindow( "strokeRect", Rect( 128, 64, 360, 360 ));
w.drawHook = {
var r;
JPen.translate( 0.5, 0.5 ); // !
r = Rect( 100, 100, 160, 80 );
JPen.color = Color.black.alpha_( 0.8 );
JPen.strokeRect( r );
};
w.front;
)
JPen.fillRect( <(Rect) rect> )
Fills the area of a rectangle. Example:
(
w = JSCWindow( "fillRect", resizable: false );
w.view.background = Color.white;
w.drawHook = {
var r;
200.do({
JPen.color = Color.black.alpha_( 1.0.rand.pow( 6 ));
JPen.fillRect( Rect( 200.0.rand, 200.0.rand, 200.0.rand, 200.0.rand ));
});
};
w.front;
)
(
var run = true;
w.onClose = { run = false }; // closing window stops animation
{ while { run } { w.refresh; 0.2.wait }}.fork( AppClock );
)
JPen.strokeOval( <(Rect) rect> )
Strokes the outline of an ellipse. The ellipse is specified by its bounding (framing) rectangle. Example:
(
w = JSCWindow( "strokeOval", Rect( 128, 64, 360, 360 ));
w.drawHook = {
var h, v, r;
v = h = 300.0;
r = Rect( 100, 100, 160, 80 );
JPen.width = 10;
JPen.color = Color.black.alpha_( 0.8 );
JPen.strokeOval( r );
};
w.front;
)
JPen.fillOval( <(Rect) rect> )
Fills the area of an ellipse. The ellipse is specified by its bounding (framing) rectangle. Example:
(
w = JSCWindow( "fillOval", resizable: false );
w.view.background = Color.black;
w.drawHook = {
var r;
200.do({
JPen.color = Color.white.alpha_( 1.0.rand.pow( 6 ));
JPen.fillOval( Rect( 200.0.rand, 200.0.rand, 200.0.rand, 200.0.rand ));
});
};
w.front;
)
(
var run = true;
w.onClose = { run = false }; // closing window stops animation
{ while { run } { w.refresh; 0.2.wait }}.fork( AppClock );
)
These commands draw text. The font is configured by calling JPen.font_( <(JFont) font> ). The text color is taken from JPen.fillColor_( <(Color) c> ).
JPen.stringAtPoint( <(String) str>, <(Point) point> )
Draws a text str string left aligned from a given point. There are no line breaks, and the text is vertically aligned such that the given y position referes to the top of the text. Example:
(
w = JSCWindow( "stringAtPoint", Rect( 128, 64, 360, 360 ), false );
w.drawHook = {
var pt;
JPen.font = JFont( "Monospaced", 16 );
JPen.strokeColor = Color.red;
12.do({
pt = Point( rrand( 10, 280 ), rrand( 10, 340 ));
JPen.stringAtPoint( "Evidence", pt );
JPen.moveTo( pt.translate( -8 @ 0 ));
JPen.lineTo( pt.translate( -2 @ 0 ));
JPen.moveTo( pt.translate( 0 @ (-8) ));
JPen.lineTo( pt.translate( 0 @ (-2) ));
JPen.moveTo( pt.translate( 2 @ 0 ));
JPen.lineTo( pt.translate( 8 @ 0 ));
JPen.moveTo( pt.translate( 0 @ 2 ));
JPen.lineTo( pt.translate( 0 @ 8 ));
JPen.stroke;
});
};
w.front;
)
JPen.stringInRect( <(String) str>, <(Rect) rect> ) // left/top aligment JPen.stringCenteredIn( <(String) str>, <(Rect) rect> ) // x-centered/y-centered aligment JPen.stringLeftJustIn( <(String) str>, <(Rect) rect> ) // left/y-centered aligment JPen.stringRightJustIn( <(String) str>, <(Rect) rect> ) // right/y-centered aligment
Draws a text str string inside a bouding box rect with the given alignment. Example:
(
w = JSCWindow( "String in Rect", Rect( 128, 64, 300, 560 ), false ).front;
w.view.background_(Color.white);
w.drawHook = {
var rect, txt;
// umlaute and accents don't work at the moment!
txt = "Il arrive que la realite soit trop complexe pour la transmission orale.";
JPen.font = JFont( "SansSerif", 14 );
JPen.strokeColor = Color.blue;
rect = Rect( 50, 50, 200, 100 );
JPen.stringInRect( txt, rect );
JPen.strokeRect( rect );
rect = rect.moveBy( 0, rect.height + 20 );
JPen.stringLeftJustIn( txt, rect );
JPen.strokeRect( rect );
rect = rect.moveBy( 0, rect.height + 20 );
JPen.stringCenteredIn( txt, rect );
JPen.strokeRect( rect );
rect = rect.moveBy( 0, rect.height + 20 );
JPen.stringRightJustIn( txt, rect );
JPen.strokeRect( rect );
};
)
The following commands transform the graphics state, i.e. they effect all subsequent drawing commands. These transformations are cumulative, i.e. each command applies to the previous graphics state, not the original one.
JPen.translate( <(Number) x>, <(Number) y> )
Translate the coordinate system to have its origin moved by x, y. Note that this is a concatenation with the previous affine transforms, so for example if the coordinate system had previously been rotated by 90 degrees, translating along the x-axis actually moves the origin vertically. Example:
(
w = JSCWindow.new.front;
w.view.background_( Color.white );
w.drawHook = {
// set the Color
JPen.color = Color.blue;
JPen.translate( 200, 100 );
// 0@0 in current context is now 200@100 regarding the whole view
JPen.moveTo( 0 @ 0 );
JPen.lineTo( 50 @ 100 );
JPen.lineTo( 100 @ 100 );
JPen.lineTo( 0 @ 150 );
JPen.lineTo( -100 @ 100 );
JPen.lineTo( -50 @ 100 );
JPen.lineTo( 0 @ 0 );
JPen.stroke;
};
)
// cumulative translations
(
w = JSCWindow.new.front;
w.drawHook = {
// set the Color
JPen.color = Color.black;
// draw 35 lines
35.do {
JPen.moveTo( 0 @ 0 );
JPen.lineTo( 50 @ 350 );
// shift 10 to the right every time
JPen.translate( 10, 0 );
JPen.stroke;
};
};
)
JPen.scale( <(Number) x>, <(Number) y> )
Scales subsequent drawing. x and y are scaling factors (i.e. 0.5 is half size or zoom-out, 2 is double size or zoom-in, etc.). Notice that the drawing "pen tip" scales accordingly (see also the *width section). Example:
(
w = JSCWindow.new.front;
w.view.background_( Color.white );
w.drawHook = {
// set the Color
JPen.color = Color.green;
JPen.translate( 200, 100 );
JPen.scale( 0.5, 2 );
// you have to set a starting point...
JPen.moveTo( 0 @ 0 );
JPen.lineTo( 50 @ 100 );
JPen.lineTo( 100 @ 100 );
JPen.lineTo( 0 @ 150 );
JPen.lineTo( -100 @ 100 );
JPen.lineTo( -50 @ 100 );
JPen.lineTo( 0 @ 0 );
JPen.stroke;
};
)
JPen.skew(<(Number) x>, <(Number) y> )
Skews subsequent drawing. A value of zero means no skewing. Negative x values skew the bottom to the left, negative y values skew the right to the top. Positive x values skew the bottom to the right, positive y values skew the right to the bottom. Example:
(
w = JSCWindow.new.front;
w.view.background_( Color.white );
w.drawHook = {
// set the Color
JPen.color = Color.green( 0.5, 0.8 );
JPen.translate( 200, 100 );
JPen.skew( 0.5, 0.2 );
// you have to set a starting point...
JPen.moveTo( 0 @ 0 );
JPen.lineTo( 50 @ 100 );
JPen.lineTo( 100 @ 100 );
JPen.lineTo( 0 @ 150 );
JPen.lineTo( -100 @ 100 );
JPen.lineTo( -50 @ 100 );
JPen.lineTo( 0 @ 0 );
JPen.stroke;
};
)
JPen.rotate( <(Number) angle>, <(Number) x = 0>, <(Number) y = 0> )
Rotates subsequent drawing around the Point x @ y by the amount angle in radians [0..2pi] (in clockwise orientation). Example:
(
w = JSCWindow.new.front;
w.view.background_( Color.white );
c = 0;
w.drawHook = {
JPen.translate( 220, 200 );
10.do({
JPen.translate( 0, 10 );
// set the Color for all "real" drawing
JPen.color = Color.hsv( c.fold( 0, 1 ), 1, 1, 0.5 );
// you have to set a starting point...
JPen.moveTo( 0 @ 0 );
JPen.lineTo( 50 @ 100 );
JPen.lineTo( 100 @ 100 );
JPen.lineTo( 0 @ 150 );
JPen.lineTo( -100 @ 100 );
JPen.lineTo( -50 @ 100 );
JPen.lineTo( 0 @ 0 );
JPen.fill;
JPen.rotate( 0.2pi );
c = c + 0.1;
});
c = c - 0.95; // for subsequent animation
};
)
(
var run = true;
w.onClose = { run = false }; // closing window stops animation
{ while { run } { w.refresh; 0.05.wait }}.fork( AppClock );
)
JPen.matrix( <(Array) array> )
Transforms (re-sets) the coordinate system, using the coefficients of an affine-transform matrix.
array = [a, b, c, d, x, y]
a : zoomX (1.0 = no zooming, 0.5 = zoom out, 2.0 = zoom in)b : shearingY (0.0 = no shearing)c : shearingXd : zoomYx : translateX (0.0 = no translation. positive values = move right, negative values = move left)y : translateY (0.0 = no translation. positive values = move down, negative values = move up)
Successive calls of *matrix concatenate with the previous matrix. Thus, if you need to undo the transforms, you should make use of the *use method (see below). Example:
(
var controlWindow, w;
var r, a, b, c, d, matrix = [1, 0, 0, 1, 10, 10];
var sliders, spex, name;
w = JSCWindow.new.front;
w.view.background_(Color.white);
// create a controller-window
controlWindow = JSCWindow("matrix controls", Rect(400,200,330,160), resizable: false );
controlWindow.front;
// determine the rectangle to be drawn
r = Rect.fromPoints(a = 0 @ 0, c = 180 @ 180);
b = r.leftBottom;
d = r.rightTop;
// the drawHook
w.drawHook = {
JPen.color = Color.red;
JPen.matrix = matrix; // !
JPen.width = 5;
JPen.strokeRect(r);
JPen.strokeOval(r);
JPen.color = Color.blue;
JPen.width = 0.25;
JPen.line(a, c);
JPen.line(b, d);
JPen.stroke;
JPen.font = JFont( "Helvetica-Bold", 12 );
JPen.fillColor = Color.black;
JPen.stringAtPoint( "A", a - 6 );
JPen.stringAtPoint( "B", b - 6 );
JPen.stringAtPoint( "C", c - (0 @ 6) );
JPen.stringAtPoint( "D", d - (0 @ 6) );
JPen.font = JFont( "Helvetica-Bold", 10 );
JPen.stringInRect( "a matrix test", r.moveBy( 50, 50 ));
};
controlWindow.view.decorator = sliders = FlowLayout(controlWindow.view.bounds);
spex = [
[ -2.0, 2.0 ].asSpec,
[ -2.0, 2.0 ].asSpec,
[ -2.0, 2.0 ].asSpec,
[ -2.0, 2.0 ].asSpec,
[ -200.0, 200.0 ].asSpec,
[ -200.0, 200.0 ].asSpec
];
name = #[ zoomX, shearingY, shearingX, zoomY, translateX, translateY ];
6.do { arg i;
JEZSlider( controlWindow, 300 @ 22, name[i], spex[i], { arg ez; var val;
val = ez.value;
[ i, val.round(10e-4) ].postln;
matrix[ i ] = val;
w.refresh; // reevaluate drawHook function
}, matrix[ i ]);
sliders.nextLine;
};
)
JPen.width_( <(Number) width = 1> )
Sets the width in pixels of the pen for the successive *stroke calls. The pen "tip" is additionally affected by affine transforms such as scaling and skewing. Example:
(
w = JSCWindow.new.front;
w.view.background_( Color.white );
w.drawHook = {
// set the Color
JPen.color = Color.blue( 0.5, 0.5 );
JPen.translate( 200,100 );
JPen.width = 10;
// you have to set a starting point...
JPen.moveTo( 0 @ 0 );
JPen.lineTo( 50 @ 100 );
JPen.lineTo( 100 @ 100 );
JPen.lineTo( 0 @ 150 );
JPen.lineTo( -100 @ 100 );
JPen.lineTo( -50 @ 100 );
JPen.lineTo( 0 @ 0 );
JPen.stroke;
};
)
JPen.use( <(Function) function> )
Stores the current graphics state, executes a function, and then revert to the previous graphics state. This allows you to make complex transformations of the graphics state (e.g. affine transforms, colour changes etc.) without having to explicitly revert to get back to 'normal'. Example:
(
// modified by an example of Stefan Wittwer
w = JSCWindow.new.front;
w.view.background_( Color.white );
w.drawHook = {
//paint origin
JPen.color = Color.gray( 0, 0.5 );
JPen.addArc( 0 @ 0, 20, 0, 2pi );
JPen.fill;
JPen.width = 10;
JPen.use({ // draw something complex...
JPen.width = 0.5;
JPen.translate( 100, 100 );
JPen.color = Color.blue;
JPen.addArc( 0 @ 0, 10, 0, 2pi );
JPen.fill;
20.do {
JPen.moveTo( 0 @ 0 );
JPen.lineTo( 100 @ 0 );
JPen.color = Color.red( 0.8, rrand( 0.7, 1 ));
JPen.stroke;
JPen.skew( 0, 0.1 );
};
});
// now go on with all params as before
// translation, skewing, width, and color modifications do not apply
JPen.line( 10 @ 120, 300 @ 120);
JPen.stroke;
};
)
JPen.beginPath
Discards any previously defined path. Example:
// incomplete arrow
(
w = JSCWindow.new.front;
w.view.background_( Color.white );
w.drawHook = {
// set the Color
JPen.color = Color.blue;
JPen.translate( 200, 100 );
JPen.moveTo( 0 @ 0 );
JPen.lineTo( 50 @ 100 );
JPen.lineTo( 100 @ 100 );
// forget what we just drew (the pink lines)
JPen.beginPath;
JPen.moveTo( 100 @ 100 );
JPen.lineTo( 0 @ 150 );
JPen.lineTo( -100 @ 100 );
JPen.lineTo( -50 @ 100 );
JPen.lineTo( 0 @ 0 );
JPen.stroke;
};
)
JPen.clip
Uses the previously defined path as a clipping shape, that is drawing successive shapes will clip them so that only the parts inside the clipping shape are visible. Concatenative calls of *clip intersect with the previous clipping. Thus, if you need to undo clippings, you must make use of the *use method (see above).. Example:
(
w = JSCWindow.new.front;
w.view.background_( Color.white );
w.drawHook = {
// outline the clipping path
JPen.addRect( Rect( 110, 110, 180, 30 ));
JPen.addRect( Rect( 110, 145, 180, 30 ));
JPen.addRect( Rect( 110, 180, 180, 30 ));
JPen.addRect( Rect( 110, 215, 180, 30 ));
// now clip
JPen.clip;
// everything else we draw is now clipped
JPen.color = Color.yellow;
JPen.fillRect( Rect( 0, 0, 400, 400 ));
JPen.color = Color.red;
JPen.moveTo( 200 @ 100 );
JPen.lineTo( 250 @ 200 );
JPen.lineTo( 300 @ 200 );
JPen.lineTo( 200 @ 250 );
JPen.lineTo( 100 @ 200 );
JPen.lineTo( 150 @ 200 );
JPen.fill;
};
)
JPen.setSmoothing( <(Boolean) flag> )
Turns on/off anti-aliasing. WARNING: non-antialiased string drawing currently looks very different in cocoa and swing (much thicker in swing). Example:
(
w = JSCWindow.new.front;
w.view.background_( Color.white );
w.drawHook = {
JPen.width = 2;
2.do({ arg i;
JPen.setSmoothing( i == 1 );
JPen.strokeOval( Rect( 100, 100, 50, 50 ));
JPen.moveTo( 100 @ 200 );
JPen.quadCurveTo( 200 @ 300, 100 @ 300 );
JPen.stroke;
JPen.translate( 100, 0 );
});
};
)
( // simple rotating and scaling w = JSCWindow( "Pen Rotation and Scaling", Rect( 128, 64, 360, 360 )); w.drawHook = { var h, v; v = h = 300.0; JPen.use({ // use the same rect for everything, just scale and rotate var r = Rect( 0, 0, 200, 80 ); JPen.color = Color.black; // offset all subsequent co-ordinates JPen.translate( 80, 20 ); JPen.fillRect( r ); JPen.color = Color.red; // scale all subsequent co-ordinates JPen.scale( 0.8, 0.8 ); JPen.translate( 8, 10 ); // rotate all subsequent co-ordinates JPen.rotate( 0.1pi ); JPen.fillRect( r ); JPen.color = Color.blue; // lather, rinse, repeat JPen.scale( 0.8, 0.8 ); JPen.rotate( 0.1pi ); JPen.width = 3; JPen.strokeRect( r ); JPen.color = Color.yellow( 1, 0.5 ); JPen.scale( 0.8, 0.8 ); JPen.rotate( 0.1pi ); JPen.translate( 20, -20 ); JPen.fillOval( r ); }); }; w.front; ) // redraw at random interval // different every time ( var w, run = true; w = JSCWindow("my name is... panel", Rect(128, 64, 800, 800)); w.view.background = Color.white; w.onClose = { run = false }; w.front; w.drawHook = { JPen.use { JPen.width = 0.2; 400.do { JPen.beginPath; JPen.moveTo(Point(10.rand * 80 + 40, 10.rand * 80 + 40)); JPen.lineTo(Point(10.rand * 80 + 40, 10.rand * 80 + 40)); JPen.stroke; }; }; }; { while { run } { w.refresh; 1.0.rand.wait } }.fork(AppClock) ) ( var w, run = true; w = JSCWindow("my name is... panel", Rect(128, 64, 800, 500)); w.view.background = Color.white; w.onClose = { run = false }; w.front; w.drawHook = { JPen.use { JPen.width = 2; 80.do { JPen.width = rrand(0,4) + 0.5; JPen.beginPath; JPen.moveTo(Point(800.rand, 500.rand)); JPen.lineTo(Point(800.rand, 500.rand)); JPen.stroke; }; }; }; { while { run } { w.refresh; 1.0.rand.wait } }.fork(AppClock) ) // Animation // Uses random seed to 'store' data // By reseting the seed each time the same random values and shapes are generated for each 'frame' // These can then be subjected to cumulative rotation, etc., by simply incrementing the phase var. ( // By James McCartney (slightly modified) var w, h = 700, v = 700, seed, run = true, phase = 0, bg; w = JSCWindow("wedge", Rect(40, 40, h, v), false); bg = Color.rand(0,0.3); w.onClose = { run = false }; // stop the thread on close w.front; // store an initial seed value for the random generator seed = Date.seed; w.drawHook = { JPen.width = 2; JPen.use { // reset this thread's seed for a moment thisThread.randSeed = Date.seed; // now a slight chance of a new seed or background color if (0.006.coin) { seed = Date.seed }; if (0.02.coin) { bg = Color.rand( 0, 0.3 )}; JPen.fillColor = bg; JPen.fillRect( Rect( 0, 0, h, v )); // preferably do this rather than switching w.view.background // either revert to the stored seed or set the new one thisThread.randSeed = seed; // the random values below will be the same each time if the seed has not changed // only the phase value has advanced JPen.translate(h/2, v/2); // rotate the whole image // negative random values rotate one direction, positive the other JPen.rotate(phase * 1.0.rand2); // scale the rotated y axis in a sine pattern JPen.scale(1, 0.3 * sin(phase * 1.0.rand2 + 2pi.rand) + 0.5 ); // create a random number of annular wedges rrand(6,24).do { JPen.color = Color.rand( 0.0, 1.0 ).alpha_( rrand( 0.1, 0.7 )); JPen.beginPath; JPen.addAnnularWedge(Point(0,0), a = rrand(60,300), a + 50.rand2, 2pi.rand + (phase * 2.0.rand2), 2pi.rand); if (0.5.coin) {JPen.stroke}{JPen.fill}; }; }; }; // fork a thread to update 20 times a second, and advance the phase each time { while { run } { w.refresh; 0.05.wait; phase = phase + 0.01pi;} }.fork(AppClock) ) ( var w, phase = 0, seed = Date.seed, run = true; w = JSCWindow("my name is... panel", Rect(128, 64, 800, 800)); w.view.background = Color.blue(0.4); w.onClose = { run = false }; w.front; w.drawHook = { JPen.use { if (0.02.coin) { seed = Date.seed }; thisThread.randSeed = seed; JPen.color = Color.white; 200.do { var a = 4.rand; var b = 24.rand; var r1 = 230 + (50 * a); var a1 = 2pi / 24 * b + phase; var r2 = 230 + (50 * (a + 1.rand2).fold(0,3)); var a2 = 2pi / 24 * (b + (3.rand2)).wrap(0,23) + phase; JPen.width = 0.2 + 1.0.linrand; JPen.beginPath; JPen.moveTo(Polar(r1, a1).asPoint + Point(400,400)); JPen.lineTo(Polar(r2, a2).asPoint + Point(400,400)); JPen.stroke; }; thisThread.randSeed = Date.seed; 40.do { var a = 4.rand; var b = 24.rand; var r1 = 230 + (50 * a); var a1 = 2pi / 24 * b + phase; var r2 = 230 + (50 * (a + 1.rand2).fold(0,3)); var a2 = 2pi / 24 * (b + (3.rand2)).wrap(0,23) + phase; JPen.width = 0.2 + 1.5.linrand; JPen.beginPath; JPen.moveTo(Polar(r1, a1).asPoint + Point(400,400)); JPen.lineTo(Polar(r2, a2).asPoint + Point(400,400)); JPen.stroke; }; }; }; { while { run } { w.refresh; 0.1.wait; phase = phase + (2pi/(20*24)) } }.fork(AppClock) ) // note: on a fast machine try // ... and replace the loop by 600.do // hmmmmm! ( var w, h = 800, v = 600, seed = Date.seed, phase = 0, zoom = 0.7, zoomf = 1, run = true; w = JSCWindow("affines", Rect(40, 40, h, v), resizable: false ); w.view.background = Color.blue(0.4); w.onClose = { run = false }; w.front; w.drawHook = { thisThread.randSeed = Date.seed; if (0.0125.coin) { seed = Date.seed; phase = 0; zoom = 0.7; zoomf = exprand(1/1.01, 1.01) } { phase = phase + (2pi/80); zoom = zoom * zoomf }; thisThread.randSeed = seed; JPen.use { var p1 = Point(20.rand2 + (h/2), 20.rand2 + (v/2)); var p2 = Point(20.rand2 + (h/2), 20.rand2 + (v/2)); var xscales = { exprand(2** -0.1, 2**0.1) } ! 2; var yscales = { exprand(2** -0.1, 2**0.1) } ! 2; var xlates = { 8.rand2 } ! 2; var ylates = { 8.rand2 } ! 2; var rots = { 2pi.rand + phase } ! 2; var xform; xscales = (xscales ++ (1/xscales)) * 1; yscales = (yscales ++ (1/yscales)) * 1; xlates = xlates ++ xlates.neg; ylates = ylates ++ xlates.neg; rots = rots ++ rots.neg; xform = {|i| [xlates[i], ylates[i], rots[i], xscales[i], yscales[i]] } ! 4; JPen.color = Color.grey( 1, 0.5 ); JPen.width = 8.linrand + 1; JPen.translate(400, 400); JPen.scale(zoom, zoom); JPen.translate(-400, -400); // JJJ OK this is too heavy // 1200.do { } 200.do { var p, rot, xlate, ylate, xscale, yscale; JPen.width = 8.linrand + 1; JPen.beginPath; #rot, xlate, ylate, xscale, yscale = xform.choose; JPen.translate(xlate, ylate); JPen.rotate(rot, h/2, v/2); JPen.scale(xscale, yscale); JPen.moveTo(p1); JPen.lineTo(p2); JPen.stroke; }; }; }; { while { run } { w.refresh; 0.05.wait }}.fork(AppClock) ) // gimmick by nick collins (from some sc-users list posting): ( var linetext, drawletter; var w, h = 800, v = 60, seed = Date.seed, run = true; var time, name, sourcestring; var yellowness, penwidth; //name=[\s,\u,\p,\e,\r,\c,\o,\l,\l,\i,\d,\e,\r]; //sourcestring= "any lower case text"; sourcestring= "welcome to supercollider"; name=Array.fill(sourcestring.size,{arg i; sourcestring[i].asSymbol}); time=0; linetext=('a':[[[0,1],[0.5,0]],[[0.5,0],[1,1]],[[0.25,0.5],[0.75,0.5]]],'b':[[[0,1],[0,0]],[[0,1],[1,1]],[[0,0],[1,0]],[[0,0.5],[0.75,0.5]],[[0.75,0.5],[1,0.75]],[[0.75,0.5],[1,0.25]],[[1,0.75],[1,1]],[[1,0.25],[1,0]]],'c':[[[0,1],[0,0]],[[0,0],[1,0]],[[0,1],[1,1]]],'d':[[[0,1],[0,0]],[[0,0],[0.75,0]],[[0,1],[0.75,1]],[[0.75,1],[1,0.75]],[[0.75,0],[1,0.25]],[[1,0.25],[1,0.75]]],'e':[[[0,0],[0,1]],[[0,0],[1,0]],[[0,1],[1,1]],[[0,0.5],[1,0.5]]],'f':[[[0,0],[0,1]],[[0,0],[1,0]],[[0,0.5],[1,0.5]]],'g':[[[0,1],[0,0]],[[0,0],[1,0]],[[0,1],[1,1]],[[1,1],[1,0.5]],[[0.5,0.5],[1,0.5]]],'h':[[[0,1],[0,0]],[[0,0.5],[1,0.5]],[[1,1],[1,0]]],'i':[[[0,0],[1,0]],[[0.5,0],[0.5,1]],[[0,1],[1,1]]],'j':[[[0,0],[1,0]],[[0.5,0],[0.5,1]],[[0,1],[0.5,1]]],'k':[[[0,1],[0,0]],[[0,0.5],[1,1]],[[0,0.5],[1,0]]],'l':[[[0,1],[0,0]],[[0,1],[1,1]]],'m':[[[0,1],[0,0]],[[0,0],[0.5,0.5]],[[0.5,0.5],[1,0]],[[1,0],[1,1]]],'n':[[[0,1],[0,0]],[[0,0],[1,1]],[[1,1],[1,0]]],'o':[[[0,1],[0,0]],[[0,0],[1,0]],[[0,1],[1,1]],[[1,0],[1,1]]],'p':[[[0,0],[0,1]],[[0,0],[1,0]],[[0,0.5],[1,0.5]],[[1,0],[1,0.5]]],'q':[[[0,0],[0,0.75]],[[0,0],[0.75,0]],[[0,0.75],[0.75,0.75]],[[0.75,0],[0.75,0.75]],[[0.5,0.5],[1,1]]],'r':[[[0,0],[0,1]],[[0,0],[1,0]],[[0,0.5],[1,0.5]],[[1,0],[1,0.5]],[[0,0.5],[1,1]]],'s':[[[0,0],[0,0.5]],[[0,0],[1,0]],[[0,1],[1,1]],[[0,0.5],[1,0.5]],[[1,0.5],[1,1]]],'t':[[[0,0],[1,0]],[[0.5,0],[0.5,1]]],'u':[[[0,1],[0,0]],[[0,1],[1,1]],[[1,0],[1,1]]],'v':[[[0,0],[0.5,1]],[[0.5,1],[1,0]]],'w':[[[0,0],[0.25,1]],[[0.25,1],[0.5,0.5]],[[0.5,0.5],[0.75,1]],[[0.75,1],[1,0]]],'x':[[[0,0],[1,1]],[[0,1],[1,0]]],'y':[[[0,0],[0.5,0.5]],[[0.5,0.5],[1,0]],[[0.5,0.5],[0.5,1]]],'z':[[[0,1],[1,0]],[[0,0],[1,0]],[[0,1],[1,1]]],(" ".asSymbol):[[[0,1],[1,1]],[[0,0.8],[0,1]],[[1,0.8],[1,1]]]); w = JSCWindow("welcome", Rect(40, 500, h, v), resizable: false ); w.view.background = Color.blue(0.5); w.onClose = { run = false }; w.front; drawletter= { arg which, startx, starty, xscale=100, yscale,prop=1.0; var data; yscale= yscale ? xscale; data= linetext[which]; prop=(round((data.size)*prop).asInteger).max(1); prop.do({ arg i; var val=data[i]; JPen.beginPath; JPen.line( Point( startx + ( xscale * val[ 0 ][ 0 ]), starty + (yscale * val[ 0 ][ 1 ])), Point( startx + (xscale * val[ 1 ][ 0 ]), starty + (yscale * val[ 1 ][ 1 ]))); JPen.stroke; }); }; yellowness=rrand(0.7,0.9); penwidth=rrand(2,3); w.drawHook = { JPen.use { var xoscil, xsizoscil,yoscil, todraw, usedtime; JPen.width= penwidth; JPen.color = Color.yellow( yellowness ); usedtime=time.min(1.0); todraw=(round((name.size)*usedtime).asInteger).max(1); todraw.do({ arg j; xoscil= sin(2*pi*time+(j*pi*0.13))*140/(1+(10*time)); yoscil= sin(2*pi*time+(j*pi*0.03))*200/(1+(200*time)); xsizoscil= time*5+5; drawletter.value( name[ j ], 50 + (25 * j) + xoscil, 10 + yoscil, xsizoscil, xsizoscil, usedtime); }); }; }; { while { time < 2.0 } { w.refresh; time=(time+0.025); //%2.0; 0.05.wait; } }.fork; // (AppClock); ) // funcadelics from Swiki (uses addField method from PenExtensions) // not very smooth on slower computers ;-C ( var a, w, i=0, run=true, cfunc, lfunc; a = Array.fill2D(10, 20, { arg i, j; (i & j + i) % j / 10 }); w = JSCWindow("si", Rect(128, 164, 460, 460), resizable: false ); w.view.background = Color.black; cfunc = { |val| Color( 0.2*i.fold(0, 0.4), val*i.fold(0, val.wrap(0, 0.4)), val*i.fold(0, 1.1), 0.5 ) }; lfunc = {|val| 1/ val ** i.fold(0, 1.1) * i.fold(val,2) * 0.5 }; w.drawHook = { i = i + 0.01; JPen.rotate(i, 240, 240); JPen.translate(100,100); JPen.addField(a, Rect(0, 0, 200, 200), \fillOval, cfunc, lfunc) }; w.front; w.onClose = { run = false }; { while { run } { w.refresh; 0.05.wait }}.fork; // (AppClock); )
redFrik drawings: http://swiki.hfbk-hamburg.de:8888/MusicTechnology/833. You'll have to replace dup( 20000 ) with dup( 2000 ), after all there's way too much OSC traffic and probably Java2D being slower than native cocoa painting ...